using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;

using NBusiness.CodeDom;
using NBusiness.CodeDom.Compiler;
using System.Reflection;
using System.Collections.Specialized;

namespace NBusiness.ESharp.Compiler
{
    [Serializable]
	public sealed class ESharpParser : EntityParser
	{
		#region Member fields and propertise
        private static Dictionary<string, Type> _fieldTypes = null;
        #endregion

        #region Get Field Types
        public static Type GetFieldType(string fieldType)
        {
            return GetFieldType(fieldType, false);
        }

        public static string[] GetFieldTypeNames()
        {
            List<string> names = new List<string>();
            foreach (string name in _fieldTypes.Keys)
            {
                if (!name.EndsWith("?")) names.Add(name);
            }
            return names.ToArray();
        }

        public static Type GetFieldType(string fieldType, bool isNullable)
        {
            if (fieldType == null)
                throw new ArgumentNullException("fieldType");

            //Lazy initialization
            if (_fieldTypes == null)
            {
                _fieldTypes = new Dictionary<string, Type>();
                _fieldTypes.Add("int", typeof(int));
                _fieldTypes.Add("int?", typeof(int?));
                _fieldTypes.Add("string", typeof(string));
                _fieldTypes.Add("string?", typeof(string));
                _fieldTypes.Add("unique", typeof(Guid));
                _fieldTypes.Add("unique?", typeof(Guid?));
                _fieldTypes.Add("bytes", typeof(byte[]));
                _fieldTypes.Add("bytes?", typeof(byte[]));
                _fieldTypes.Add("short", typeof(short));
                _fieldTypes.Add("short?", typeof(short?));
                _fieldTypes.Add("long", typeof(long));
                _fieldTypes.Add("long?", typeof(long?));
                _fieldTypes.Add("decimal", typeof(decimal));
                _fieldTypes.Add("decimal?", typeof(decimal?));
                _fieldTypes.Add("double", typeof(double));
                _fieldTypes.Add("double?", typeof(double?));
                _fieldTypes.Add("float", typeof(float));
                _fieldTypes.Add("float?", typeof(float?));
                _fieldTypes.Add("datetime", typeof(DateTime));
                _fieldTypes.Add("datetime?", typeof(DateTime?));
                _fieldTypes.Add("bool", typeof(bool));
                _fieldTypes.Add("bool?", typeof(bool?));
            }

            fieldType = (isNullable && !fieldType.EndsWith("?") ? fieldType + "?" : fieldType.TrimEnd('?'));

            Type t = null;
            if (_fieldTypes.ContainsKey(fieldType))
                t = _fieldTypes[fieldType];

            return t;
        }
		#endregion

		#region Expressions
		public static class Expressions
		{
            // TODO: change documentation to reflect syntax changes
            // Syntax changes so far:
            // DateTime -> datetime
            // create -> insert
            // retrieve -> fetch
            // action types to lowercase
            // action in to on change
            // validation in addition

            //Parsing Elements
            public static Regex Element = new Regex("([/][/].*$)|(\"[^\"\r\n]*\")|(\\s+)|([a-zA-Z0-9_]+)|([.][0-9]+)|([0-9]+[.][0-9]+)|(\\d+)|([{}[\\]();:=,*?.+\\-&<>!|\\/%$])", RegexOptions.Multiline);

            // Token Types
            public static Regex Keywords = new Regex("^(with|on|as|id|auto|nullable|readonly|in|when|async|family|entity|validate|access|field|validation|action|relationship|authorize|using)$");
            public static Regex Quoted = new Regex("^(\"[^\"\r\n]*\")$");
            public static Regex Literal = new Regex("^(['].[']|[0-9]+)$");
            public static Regex Delimiter = new Regex("$[.,]^");
            public static Regex WhiteSpace = new Regex(@"^\s+$");
            public static Regex LineComments = new Regex(@"^(//.*)$");
            public static Regex Identifier = new Regex(@"^[a-zA-Z]+[a-z0-9A-Z]*$"); 

			//CodeElements
			public static Regex RelationshipDeclaration = new Regex(@"^relationship$");
			public static Regex RelationshipWith = new Regex(@"^with$");
			public static Regex RelationshipOn = new Regex(@"^on$");
			public static Regex RelationshipSplit = new Regex(@",");
			public static Regex RelationshipAs = new Regex(@"^as$");
			public static Regex RelationshipType = new Regex(@"^(parent|child|sibling)$");
            public static Regex RelationshipAssign = new Regex(@"[=]");

			public static Regex FieldIdProperty = new Regex(@"^id$");
			public static Regex FieldAutoProperty = new Regex(@"^auto$");
            public static Regex FieldNullableProperty = new Regex(@"^nullable$");
            public static Regex FieldReadOnly = new Regex(@"^readonly$");
            public static Regex FieldTypes = new Regex("^(int|string|unique|bytes|short|long|decimal|double|float|datetime|bool)$");
            public static Regex FieldDeclaration = new Regex(@"^field$");

			public static Regex FamilyDeclaration = new Regex(@"^family$");
            public static Regex FamilyJoin = new Regex(@"[.]");

			public static Regex EntityDeclaration = new Regex(@"^entity$");
            public static Regex EntityAs = new Regex(@"^as$");
            public static Regex EntityTemplateSeparator = new Regex(@"[,]");

            //using declaration
            public static Regex UsingDeclaration = new Regex(@"^using$");
            public static Regex UsingJoin = new Regex("[.]");

            //action
            public static Regex ActionDeclaration = new Regex(@"^action$");
            public static Regex ActionOn = new Regex(@"^on$");
            public static Regex ActionTypeJoin = new Regex(@"[.]");
            public static Regex ActionWhen = new Regex(@"^when$");
            public static Regex ActionAsync = new Regex(@"^async$");
            public static Regex ActionWhenType = new Regex("(loading|loaded|fetching|fetched|persisting|persisted|inserting|inserted|updating|updated|deleting|deleted|validating|validated|authorizing|authorized|accessing|accessed|changed|error)");

            //rules
            public static Regex AccessDeclaration = new Regex(@"^access$");
            public static Regex AccessType = new Regex(@"^(get|set)$");

            public static Regex AuthorizeDeclaration = new Regex(@"^authorize$");
            public static Regex AuthorizeType = new Regex(@"^(insert|fetch|update|delete)$");

			public static Regex ValidateDeclaration = new Regex(@"^validate$");
            public static Regex ValidateArgSplit = new Regex(@"[,]");
            public static Regex ValidateArg = new Regex("^(([\"].*[\"])|(['].['])|([a-zA-Z]+[0-9]?)|(\\d+)|([.][0-9]+)|([0-9]+[.][0-9]+))$");

            //misc
			public static Regex DenyAllow = new Regex(@"^(deny|allow)$");
            public static Regex RoleName = new Regex("^([\"][a-zA-Z](\\w|[ ])*[\"])|([a-zA-Z](\\w|[ ])*)|[*]|[?]");
            public static Regex GroupBegin = new Regex(@"{");
            public static Regex GroupEnd = new Regex(@"}");
            public static Regex StatementEnd = new Regex(@";");
            public static Regex Command = new Regex(@"[{}[\]();:=,*?.+\-&<>!|\\/%$]");
		} 
		#endregion

        #region Parse

        #region Parse overrides
        /// <summary>
        /// Parse the given files. File IO is performed to read text from the
        /// supplied files.
        /// </summary>
        /// <param name="Files">The files to read from.</param>
        /// <returns>The built and attached root that is built.</returns>
        public override EntityRoot ParseFiles(params string[] Files)
        {
            EntityRoot root = new ESharpRoot();
            foreach (string file in Files)
            {
                string body = File.ReadAllText(file);
                BuildRoot(root, body, file);
            }
            root.Attach();
            return root;
        }
        /// <summary>
        /// Parse the given code. No filenames are tracked.
        /// </summary>
        /// <param name="code">the code to parse</param>
        /// <returns>The built and attached root.</returns>
        public override EntityRoot ParseCode(params string[] code)
        {
            EntityRoot root = new ESharpRoot();
            foreach (string body in code)
            {
                BuildRoot(root, body, null);
            }
            root.Attach();
            return root;
        }
        #endregion

        #region Build root
        /// <summary>
        /// Builds an unattached root based on the code read from the given path. 
        /// No file IO is performed.
        /// </summary>
        /// <param name="root">The root to build up.</param>
        /// <param name="code">The code to parse.</param>
        /// <param name="path">The path the code was found at. (can be null)</param>
        public void BuildRoot(EntityRoot root, string code, string path)
        {
            if (root == null)
                throw new ArgumentNullException("root");
            if (code == null)
                throw new ArgumentNullException("code");
            //path can be null

            root.BuildError -= new EventHandler<EntityBuildErrorEventArgs>(child_ParseError);
            root.BuildError += new EventHandler<EntityBuildErrorEventArgs>(child_ParseError);

            //Push elements on a stack
            EntityElement[] elements = GetElements(code);
            Stack<EntityElement> stack = new Stack<EntityElement>();
            for (int x = elements.Length - 1; x >= 0; x--)
            {
                elements[x].Path = path;
                stack.Push(elements[x]);
            }

            root.Build(stack, EntityElementType.Unknown);
		}

		#endregion

        #region Get Elements
        public static EntityElement[] GetElements(string code)
        {
            List<EntityElement> elements = new List<EntityElement>();

            int index = 0;
            int len = 0;
            MatchCollection matches = Expressions.Element.Matches(code);

            foreach(Match match in matches)
            {
                if (match.Index == index)
                {
                    len = match.Value.Length;
                }
                else
                {
                    len = match.Index - index;
                }

                string val = code.Substring(index, len);
                int line = ESharpRoot.GetLine(code, index);
                int col = ESharpRoot.GetColumn(code, index);
                EntityElement element = new EntityElement(val, line, col, index, index + len-1);
                index += len;

                elements.Add(element);
            }

            return elements.ToArray();
        }
        #endregion

        #endregion

        #region Errors
        public event EventHandler<EntityBuildErrorEventArgs> ParserError;
        private void OnParserError(EntityBuildError Error)
        {
            if (ParserError != null) ParserError(this, new EntityBuildErrorEventArgs(Error));
        }

        void child_ParseError(object sender, EntityBuildErrorEventArgs e)
        {
            OnParserError(e.Error);
        }
        #endregion
    }
}
